package top.fullj.bean;

import com.google.common.base.Preconditions;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @author bruce.wu
 * @since 2021/6/21 10:25
 */
@SuppressWarnings("WeakerAccess")
public abstract class BeanUtils {

    public static void copyFields(Object source, Object target) {
        Preconditions.checkNotNull(source, "source must not be null");
        Preconditions.checkNotNull(target, "target must not be null");

        Map<String, Field> targetFields = getFields(target.getClass());
        Map<String, Field> sourceFields = getFields(source.getClass());
        for (Map.Entry<String, Field> entry : targetFields.entrySet()) {
            Field targetField = entry.getValue();
            boolean ignore = Modifier.isStatic(targetField.getModifiers())
                    || Modifier.isTransient(targetField.getModifiers());
            if (!ignore && sourceFields.containsKey(entry.getKey())) {
                Field sourceField = sourceFields.get(entry.getKey());
                sourceField.setAccessible(true);
                targetField.setAccessible(true);
                try {
                    Object value = sourceField.get(source);
                    if (value != null) {
                        targetField.set(target, value);
                    }
                } catch (IllegalAccessException e) {
                    throw new BeanException("cant copy field: " + entry.getKey(), e);
                }
            }
        }
    }

    public static Map<String, Field> getFields(Class<?> beanClass) {
        Map<String, Field> map = new HashMap<>();
        for (Class<?> c = beanClass; c != Object.class; c = c.getSuperclass()) {
            for (Field field : c.getDeclaredFields()) {
                String name = field.getName();
                if (!map.containsKey(name)) {
                    map.put(name, field);
                }
            }
        }
        return map;
    }

    public static Map<Object, String> describe(Class<?> type) {
        Map<Object, String> map = new HashMap<>();
        try {
            for (Field field : type.getDeclaredFields()) {
                Object value = field.get(null);
                String text;
                if (field.isAnnotationPresent(Describe.class)) {
                    text = field.getAnnotation(Describe.class).value();
                } else {
                    text = field.getName();
                }
                map.put(value, text);
            }
        } catch (ReflectiveOperationException e) {
            throw new BeanException("unable to describe: " + type.getName(), e);
        }
        return Collections.unmodifiableMap(map);
    }

}
